package pl.edu.pk.mech.kkbus.datastore.common;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.api.memcache.MemcacheService;

/**
 * @author O.Kamil
 *
 */
public class SynchronizeCommon {
	  private SynchronizeCommon(){};
	  private static final Logger logger = Logger.getLogger(SynchronizeCommon.class.getCanonicalName());
	  
	  private static DatastoreService datastore = ServiceFactory.getDatastoreService();
	  private static MemcacheService keycache = ServiceFactory.getMemcacheService();

	 /**
	 * @param entity
	 * Dodanie encji do cache i datastore
	 */
	public static boolean putEntityCacheAndStore(Entity entity) {
	    logger.log(Level.INFO, "Dodanie encji do cache i datastore "+entity.getKey());
	    Key key = entity.getKey();
	    Transaction txn = datastore.beginTransaction();
	    try {
	      datastore.put(entity);
		  txn.commit();
	    } finally {
		  if (txn.isActive()) {
		    txn.rollback();
		    logger.log(Level.WARNING, "rollback encji: "+entity.getKey());
		  } else {
			addToCache(key, entity);
			return true;
		  }
	    }
	    return false;
	  }
	
	 /**
	 * @param entity
	 * Dodanie encji do datastore
	 */
	public static void putEntityStore(Entity entity) {
	    logger.log(Level.INFO, "Dodanie do datastore encji");
	    Transaction txn = datastore.beginTransaction();
	    try {
	      datastore.put(entity);
		  txn.commit();
	    } finally {
		  if (txn.isActive()) {
		    txn.rollback();
		    logger.log(Level.WARNING, "rollback encji: "+entity.getKey());
		  }
	    }
	  }

     /**
	 * @param key
	 * Usuniecie encji z cache i datastore za pomoca key
	 */
	public static boolean deleteEntity(Key key) {
		logger.log(Level.INFO, "Usuniecie encji z cache i datastore za pomoca "+key);
		Transaction txn = datastore.beginTransaction();
		try {
		  datastore.delete(key);
		  txn.commit();
		} finally {
		  if (txn.isActive()) {
		    txn.rollback();
		    logger.log(Level.WARNING, "Usuniecie encji - rollback klucz: "+key);
		    return false;
		  } else {
			deleteFromCache(key);
			return true;
		  }
	    }
	  }

	 /**
	 * @param keys
	 * Usuwa encje wedlug listy kluczy z datastore oraz cache
	 */
	public static void deleteEntity(final List<Key> keys) {
		logger.log(Level.INFO, "Usuniecie encji z cache i datastore za pomoca listy kluczy");
	    datastore.delete(new Iterable<Key>() {
	    	public Iterator<Key> iterator() {
			  return keys.iterator();
		  }
		});
	    deleteFromCache(keys);
	  }

     /**
	 * @param key
	 * @return zwraca encje wedlug podanego klucza
	 * w pierwszej kolejnosci przeszukuje cache, nastepnie datastore
	 */
	public static Entity findEntity(Key key) {
		logger.log(Level.INFO, "Szukam encji o kluczu: "+key);
		try {
		  Entity entity = getFromCache(key);
		  if (entity != null) {
			return entity;
		  }
		  return datastore.get(key);
		} catch (EntityNotFoundException e) {
			logger.log(Level.WARNING, "Brak encji o kluczu: "+key);
			return null;
		}
	  }

	 /**
	 * @param kind
	 * @param searchBy
	 * @param searchFor
	 * @return zwraca liste encji wedlug zadanych kryteriow
	 */
	public static Iterable<Entity> listEntities(String kind, String searchBy, String searchFor) {
	    logger.log(Level.INFO, "Search entities based on search criteria");
	    Query query = new Query(kind);
	    if (searchFor != null && !"".equals(searchFor)) {
	      query.addFilter(searchBy, Query.FilterOperator.EQUAL, searchFor);
	    }
	    PreparedQuery pq = datastore.prepare(query);
	    return pq.asIterable();
	  }
	
	public static Iterable<Entity> getByID(String kind, String searchBy, ArrayList<Long> listId) {
	    logger.log(Level.INFO, "Search entities based on search criteria");
	    Query query = new Query(kind);
	    if (listId != null && searchBy!=null) {
	    	query.addFilter("name", Query.FilterOperator.IN, Arrays.asList("dadad","Kamil"));
	    }
	    PreparedQuery pq = datastore.prepare(query);
	    return pq.asIterable();
	  }

	 /**
	 * @param kind
	 * @param ancestor
	 * @return zwraca liste dzieci dla klucza nadrzednego
	 */
	public static Iterable<Entity> listChildren(String kind, Key ancestor) {
	    logger.log(Level.INFO, "Search entities based on parent");
	    Query query = new Query(kind);
	    query.setAncestor(ancestor);
	    query.addFilter(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, ancestor);
	    PreparedQuery pq = datastore.prepare(query);
	    return pq.asIterable();
	  }

	 /**
	 * @param kind
	 * @param ancestor
	 * @return zwraca liste kluczy dla wszystkich dzieci klucza nadrzednego
	 */
	public static Iterable<Entity> listChildKeys(String kind, Key ancestor) {
	    logger.log(Level.INFO, "Search entities based on parent");
	    Query query = new Query(kind);
	    query.setAncestor(ancestor).setKeysOnly();
	    query.addFilter(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN,ancestor);
	    PreparedQuery pq = datastore.prepare(query);
	    return pq.asIterable();
	  }

     /**
	 * @param key
	 * @param entity
	 * Dodanie encji do cache
	 */
	public static void addToCache(Key key, Entity entity) {
	    logger.log(Level.INFO, "Dodanie encji do cache "+entity+" o kluczu "+key);
	    keycache.put(key, entity);
	  }

	/**
	 * @param key
	 * Usuniecie encji z cache
	 */
	public static void deleteFromCache(Key key) {
	    logger.log(Level.INFO, "Usuniecie encji z cache o kluczu "+key);
	    keycache.delete(key);
	}


	/**
	 * @param keys
	 * Usuniecie encji z cache
	 */
	public static void deleteFromCache(List<Key> keys) {
	    logger.log(Level.INFO, "Usuniecie encji z cache za pomoca listy kluczy");
	    keycache.deleteAll(keys);
	}

	/**
	 * @param key
	 * @return Zwraca encje z cache za pomoca klucza
	 */
	public static Entity getFromCache(Key key) {
	    logger.log(Level.INFO, "Searching entity in cache");
	    return (Entity) keycache.get(key);
	}
}